package com.dongnam.quanly.web;

import java.io.Serializable;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.faces.application.ConfigurableNavigationHandler;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.context.FacesContext;
import javax.faces.event.ComponentSystemEvent;

import org.hibernate.Hibernate;
import org.hibernate.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.dongnam.quanly.common.ActionCategory;
import com.dongnam.quanly.common.ActionType;
import com.dongnam.quanly.common.ObjectType;
import com.dongnam.quanly.common.ScreenId;
import com.dongnam.quanly.common.ScreenType;
import com.dongnam.quanly.common.dao.ConsoleUserDao;
import com.dongnam.quanly.common.model.ConsoleUser;
import com.dongnam.quanly.common.model.UserRight;
import com.dongnam.quanly.common.model.UserRole;
import com.dongnam.quanly.common.model.UserRight.AccessLevel;
import com.dongnam.quanly.common.persistence.HibernateUtil;
import com.dongnam.quanly.common.util.CryptUtil;
import com.dongnam.quanly.web.audit.AuditLogger;
import com.dongnam.quanly.web.util.LocaleMessages;

@ManagedBean(name = "UserBean")
@SessionScoped
public class UserBean implements Serializable {

	public static final String SCREEN_LOGGED_IN = "logged_in";
	public static final String SCREEN_INACTIVE_ACCOUNT = "inactive_account";
	public static final String SCREEN_ERROR = "error";

	private static class LoginResult {
		public LoginRC rc;
		public ConsoleUser user;

		LoginResult(LoginRC rc, ConsoleUser loggedInUser) {
			this.rc = rc;
			this.user = loggedInUser;
		}
	}

	private static enum LoginRC {
		INVALID_USER, INVALID_PASSWORD, INACTIVE_ACCOUNT, ERROR,

		SUCCESS
	}

	private static final Logger logger = LoggerFactory.getLogger(UserBean.class);

	private static final long serialVersionUID = 1L;

	private String userId;
	private String password;
	private ConsoleUser user;
	private Locale locale = Locale.ENGLISH;
	private String previousViewID = "";
	private boolean isBackToLogin = true;

	public boolean isBackToLogin() {
		return isBackToLogin;
	}

	public void setBackToLogin(boolean isBackToLogin) {
		this.isBackToLogin = isBackToLogin;
	}

	private Map<ScreenType, AccessLevel> userAccessMap = null;

	private static final Map<String, ScreenType> pageToTypeId;

	public String getPreviousViewID() {
		return previousViewID;
	}

	public void setPreviousViewID(String previousViewID) {
		this.previousViewID = previousViewID;
	}

	static {
		pageToTypeId = new ConcurrentHashMap<String, ScreenType>();
		pageToTypeId.put("/pages/privilege", ScreenType.PRIVILEGE);
		pageToTypeId.put("/pages/audit", ScreenType.AUDIT);

		pageToTypeId.put("/pages/danhmuc/doiTac", ScreenType.DoiTac);
		pageToTypeId.put("/pages/danhmuc/loaiVatTuHangHoa", ScreenType.LoaiVatTuHangHoa);
		pageToTypeId.put("/pages/danhmuc/nganHang", ScreenType.NganHang);
		pageToTypeId.put("/pages/danhmuc/nhanVien", ScreenType.NhanVien);
		pageToTypeId.put("/pages/danhmuc/nhomDoiTac", ScreenType.NhomDoiTac);
		pageToTypeId.put("/pages/danhmuc/phongBan", ScreenType.PhongBan);
		pageToTypeId.put("/pages/danhmuc/taiKhoanNganHang", ScreenType.TaiKhoanNganHang);
		pageToTypeId.put("/pages/danhmuc/vatTuHangHoa", ScreenType.VatTuHangHoa);
		pageToTypeId.put("/pages/danhmuc/kho", ScreenType.Kho);

		pageToTypeId.put("/pages/dichvubaohanh/baoHanhThietBi", ScreenType.BaoHanhThietBi);
		pageToTypeId.put("/pages/dichvubaohanh/nhanThietBi", ScreenType.NhanThietBi);
		pageToTypeId.put("/pages/dichvubaohanh/suaChuaThietBi", ScreenType.SuaChuaThietBi);
		pageToTypeId.put("/pages/dichvubaohanh/traThietBi", ScreenType.TraThietBi);

		pageToTypeId.put("/pages/huongDan", ScreenType.HuongDan);

		pageToTypeId.put("/pages/nghiepvu/congno/noPhaiThu", ScreenType.NoPhaiThu);
		pageToTypeId.put("/pages/nghiepvu/congno/noPhaiTra", ScreenType.NoPhaiTra);

		pageToTypeId.put("/pages/nghiepvu/kho/lapRapThaoDo", ScreenType.LapRapThaoDo);
		pageToTypeId.put("/pages/nghiepvu/kho/nhapXuatKho", ScreenType.NhapXuatKho);
		pageToTypeId.put("/pages/nghiepvu/kho/tonKho", ScreenType.TonKho);

		pageToTypeId.put("/pages/nghiepvu/muabanhang/baoGia", ScreenType.BaoGia);
		pageToTypeId.put("/pages/nghiepvu/muabanhang/doiSoat", ScreenType.DoiSoat);
		pageToTypeId.put("/pages/nghiepvu/muabanhang/donHang", ScreenType.DonHang);
		pageToTypeId.put("/pages/nghiepvu/muabanhang/hoaDon", ScreenType.HoaDon);
		pageToTypeId.put("/pages/nghiepvu/muabanhang/trangThaiDonHang", ScreenType.TrangThaiDonHang);
		

		pageToTypeId.put("/pages/nghiepvu/quy/phieuThuChi", ScreenType.PhieuThuChi);
		pageToTypeId.put("/pages/nghiepvu/quy/soQuy", ScreenType.SoQuy);

		pageToTypeId.put("/pages/tienIch", ScreenType.TienIch);

		pageToTypeId.put("/pages/changePassword", ScreenType.FREE);
		pageToTypeId.put("/pages/index", ScreenType.FREE);
		pageToTypeId.put("/pages/error", ScreenType.FREE);
	}

	public boolean isLoggedIn() {
		return user != null;
	}

	public String logout() {
		FacesContext fctx = FacesContext.getCurrentInstance();
		if (user != null) {
			AuditLogger.logAuditTrail(ScreenId.INDEX, ActionCategory.MISC, ActionType.LOGOUT, true, ObjectType.User,
					user.getUserId(), null, null, "");
			user = null;
			fctx.getExternalContext().invalidateSession();
		}
		return "logged_out";
	}

	public ConsoleUser getUser() {
		return user;
	}

	public void setUser(ConsoleUser user) {
		this.user = user;
	}

	public void checkLogin(ComponentSystemEvent event) {
		if (isLoggedIn()) {
			FacesContext context = FacesContext.getCurrentInstance();
			ConfigurableNavigationHandler handler = (ConfigurableNavigationHandler) context.getApplication()
					.getNavigationHandler();
			handler.performNavigation("/pages/index.xhtml?faces-redirect=true");
		}
	}

	public String debugLogin() {
		// throw new DatabaseException("test exception handling");
		userId = "SYS_DEFAULT";
		password = "abc123";
		return attemptLogin();
	}

	public String attemptLogin() {
		return attemptLogin(true);
	}

	public String attemptLogin(boolean checkPassword) {
		LoginResult loginResult = attemptLogin(userId, password, checkPassword);
		logger.debug("String attemptLogin: userId: {}, password: {}", userId, password);
		String outcome = null;
		switch (loginResult.rc) {
		case SUCCESS:
			this.user = loginResult.user;
			logger.debug("attemptLogin SUCCESS, user = {}", user);
			AuditLogger.logAuditTrail(ScreenId.INDEX, ActionCategory.MISC, ActionType.LOGIN, true, ObjectType.User,
					userId, null, null, "");
			outcome = SCREEN_LOGGED_IN;
			break;
		case INACTIVE_ACCOUNT:
			logger.debug("attemptLogin FAILURE, INACTIVE_ACCOUNT,  user = {}", user);
			addErrorMsg("general_error_inactive");
			AuditLogger.logAuditTrail(ScreenId.INDEX, ActionCategory.MISC, ActionType.LOGIN, false, ObjectType.User,
					userId, "INACTIVE_ACCOUNT", null, "");
			outcome = SCREEN_INACTIVE_ACCOUNT;
			break;
		case ERROR:
			logger.debug("attemptLogin FAILURE, ERROR, user = {}", user);
			addErrorMsg("db_error_reason_other");
			AuditLogger.logAuditTrail(ScreenId.INDEX, ActionCategory.MISC, ActionType.LOGIN, false, ObjectType.User,
					userId, "ERROR", null, "");
			outcome = SCREEN_ERROR;
			break;
		default:
			logger.debug("attemptLogin FAILURE, default, user = {}", user);
			addErrorMsg("general_error_invalid");
			AuditLogger.logAuditTrail(ScreenId.INDEX, ActionCategory.MISC, ActionType.LOGIN, false, ObjectType.User,
					userId, "INVALID USER OR PASSWORD", null, "");
			outcome = SCREEN_ERROR;
			break;
		}
		userId = null;
		password = null;
		return outcome;
	}

	private LoginResult attemptLogin(String userName, String password, boolean checkPassword) {
		logger.debug("attemptLogin");
		LoginRC rc = LoginRC.SUCCESS;

		ConsoleUser consoleUser = null;
		ConsoleUserDao consoleUserDao = new ConsoleUserDao();
		boolean isTransacted = HibernateUtil.getCurrentSession().getTransaction().isActive();
		Session session = isTransacted ? HibernateUtil.getCurrentSession() : HibernateUtil.startCurrentSession();

		try {
			consoleUser = consoleUserDao.getById(userName, false);
			logger.debug("consoleUser: {}", consoleUser);
			if (consoleUser == null) {
				logger.debug("INVALID_USER");
				rc = LoginRC.INVALID_USER;
			} else if (consoleUser.getStatus() != ConsoleUser.Status.ACTIVE) {
				logger.debug("INACTIVE_ACCOUNT");
				rc = LoginRC.INACTIVE_ACCOUNT;
			} else if (checkPassword) {
				try {
					logger.debug("ConsoleUser: userId={}, password={}", consoleUser.getUserId(),
							consoleUser.getPassword());
					logger.debug("ConsoleUser: userId={}, enteredPassword={}", consoleUser.getUserId(),
							CryptUtil.encrypt(password));
					if (!CryptUtil.verify(password, consoleUser.getPassword())) {
						rc = LoginRC.INVALID_PASSWORD;
						consoleUser.setLoginFailure((consoleUser.getLoginFailure() == null ? 0 : consoleUser
								.getLoginFailure()) + 1);
					}
				} catch (Exception e) {
					e.printStackTrace();
					logger.error("attemptLogin", e);
					rc = LoginRC.ERROR;
				}
			}

			if (consoleUser != null) {
				// Initialize variable for later use
				if (consoleUser.getGroup() != null) {
					for (UserRole role : consoleUser.getGroup().getUserRoles()) {
						Hibernate.initialize(role.getUserRights());
					}
				}
				consoleUser.setLastLogin(new Timestamp(System.currentTimeMillis()));
				consoleUserDao.makePersistent(consoleUser);
			}

			if (!isTransacted) {
				session.getTransaction().commit();
			}
		} catch (Exception e) {
			e.printStackTrace();
			if (!isTransacted && session.isOpen() && !session.getTransaction().wasCommitted())
				session.getTransaction().rollback();
			String msg = LocaleMessages.getString("general_connectdbfail");
			JSFUtility.setJSFFacesMessage(FacesMessage.SEVERITY_ERROR, msg, null);
			try {
				throw e;
			} catch (Exception e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
		}
		return new LoginResult(rc, consoleUser);
	}

	private void addErrorMsg(String msgKey) {
		FacesMessage message = LocaleMessages.getMessage(msgKey);
		message.setSeverity(FacesMessage.SEVERITY_ERROR);
		FacesContext.getCurrentInstance().addMessage(null, message);
	}

	public String getUserId() {
		return userId;
	}

	public void setUserId(String userId) {
		this.userId = userId;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	// get map of highest allowed access levels, through all rights a user has
	private Map<ScreenType, AccessLevel> getUserAccessMap() {
		if (userAccessMap == null) {
			if (user != null) {
				List<UserRight> userRightList = new ArrayList<UserRight>();
				for (UserRole role : user.getGroup().getUserRoles()) {
					userRightList.addAll(role.getUserRights());
				}
				// after getting a list of user rights that belongs to user,
				// find max right per page
				userAccessMap = new ConcurrentHashMap<ScreenType, AccessLevel>();
				for (UserRight right : userRightList) {
					if (userAccessMap.containsKey(right.getTypeId())) {
						// if access level in roleList is higher, put that into
						// the access map, else do nothing
						if (right.getAccessLevel().ordinal() > userAccessMap.get(right.getTypeId()).ordinal()) {
							userAccessMap.put(right.getTypeId(), right.getAccessLevel());
						}
					} else { // put access right into map if it doesnt exist yet
						userAccessMap.put(right.getTypeId(), right.getAccessLevel());
					}
				}
			}
		}
		return userAccessMap;
	}

	private ScreenType getTypeId() {
		String uri = FacesContext.getCurrentInstance().getViewRoot().getViewId();
		for (Map.Entry<String, ScreenType> page : pageToTypeId.entrySet()) {
			if (uri.contains(page.getKey())) {
				return pageToTypeId.get(page.getKey());
			}
		}
		logger.info("getTypeId() = null, userId = {}, requestURI = {}", userId, uri);
		return null;
	}

	/**
	 * Return null if user has not logged in.
	 */
	public String getAccessLevel() {
		if (isLoggedIn()) {
			ScreenType screenType = getTypeId();
			if (screenType == null) {
				return AccessLevel.NONE.name();
			} else if (screenType == ScreenType.FREE) {
				return AccessLevel.WRITE.name();
			} else if (screenType == ScreenType.READONLY) {
				return AccessLevel.READ.name();
			} else {
				AccessLevel accessLevel = getUserAccessMap().get(screenType);
				if (accessLevel != null) {
					return accessLevel.name();
				} else {
					return AccessLevel.NONE.name();
				}
			}
		} else {
			return null;
		}
	}

	public Locale getLocale() {
		return locale;
	}

	public void setLocale(Locale locale) {
		this.locale = locale;
		FacesContext.getCurrentInstance().getViewRoot().setLocale(locale);
	}

	public void setLocale(String language, String country) {
		this.locale = new Locale(language, country);
		FacesContext.getCurrentInstance().getViewRoot().setLocale(locale);
	}

}